﻿using System;
using System.Linq;
using System.Security.Cryptography;
using System.Xml.Serialization;
using EncryptionLib.Util;

namespace EncryptionLib.Users
{
    [Serializable]
    [XmlInclude(typeof(Passphrase))]
    public abstract class PassphraseBase : IPassphrase
    {
        public abstract bool Verify(byte[] sPassphrase);
        public abstract bool GetSecretKeys(int iLen, ref byte[] arrPassword, ref byte[] arrIV);

        public PassphraseBase() { }
    }

    [Serializable]
    public class Passphrase : PassphraseBase
    {
        /// <summary>
        /// Don't set this property. It's created for XML-compatible.
        /// </summary>
        public HashAlgorithms HashAlgorithm
        {
            get;
            set;
        }

        /// <summary>
        /// Don't set this property. It's created for XML-compatible.
        /// </summary>
        public byte[] HashValue
        {
            get;
            set;
        }

        /// <summary>
        /// Don't set this property. It's created for XML-compatible.
        /// </summary>
        public byte[] Salt
        {
            get;
            set;
        }

        public Passphrase(byte[] passPhrase)
        {
            HashAlgorithm = HashAlgorithms.SHA384;

            if (passPhrase == null)
                throw new NullReferenceException("Passphrase cannot be null.");
            else
                UpdateHash(passPhrase);
        }

        public Passphrase(string sPassphrase)
            : this(SerializerHelper.String2Bytes(sPassphrase))
        {

        }

        public Passphrase()
            : this(new byte[8]{140, 141, 142, 143, 144, 145, 146, 147})
        {

        }

        public override bool Verify(byte[] sPassphrase)
        {
            if (HashValue == null || Salt == null || sPassphrase == null)
                return false;
            if (Salt.Length != sPassphrase.Length)
                return false;

            for (int i = Salt.Length - 1; i >= 0; --i)
            {
                sPassphrase[i] ^= Salt[i];
            }

            return GetHashAlgo().ComputeHash(sPassphrase).SequenceEqual(HashValue);
        }

        public override bool GetSecretKeys(int iLen, ref byte[] arrPassword, ref byte[] arrIV)
        {
            arrPassword = null;
            arrIV = null;

            if (iLen % 8 != 0)
                return false;

            iLen /= 8;
            if(HashValue != null)
            {
                arrPassword = new byte[iLen];
                arrIV = new byte[iLen];

                if(HashValue.Length >= iLen)
                {
                    Array.Copy(HashValue, arrPassword, iLen);

                    if(HashValue.Length >= 2*iLen)
                    {
                        Array.Copy(HashValue, iLen, arrIV, 0, iLen);
                    }
                    else
                    {
                        Array.Copy(HashValue, iLen, arrIV, 0, HashValue.Length - iLen);
                        for (int i = HashValue.Length - iLen; i < iLen; ++i)
                        {
                            arrIV[i] = 0x5A;
                        }
                    }
                }
                else
                {
                    Array.Copy(HashValue, arrPassword, HashValue.Length);
                    for (int i = HashValue.Length; i < iLen; ++i)
                    {
                        arrPassword[i] = 0xA5;
                    }
                    Array.Copy(HashValue, arrIV, HashValue.Length);
                    for (int i = HashValue.Length; i < iLen; ++i)
                    {
                        arrIV[i] = 0x5A;
                    }
                }
                return true;
            }
            return false;
        }

        protected void UpdateHash(byte[] passPhrase)
        {
            Random randomer = new Random((int)(DateTime.Now.Ticks % int.MaxValue));
            Salt = new byte[passPhrase.Length];
            randomer.NextBytes(Salt);
            
            for(int i = Salt.Length - 1; i >= 0; --i)
            {
                passPhrase[i] ^= Salt[i];
            }

            HashValue = GetHashAlgo().ComputeHash(passPhrase);
        }

        protected HashAlgorithm GetHashAlgo()
        {

            switch (HashAlgorithm)
            {
                case HashAlgorithms.MD5:
                    return new MD5CryptoServiceProvider();
                case HashAlgorithms.SHA1:
                    return new SHA1CryptoServiceProvider();
                case HashAlgorithms.SHA256:
                    return new SHA256Managed();
                case HashAlgorithms.SHA384:
                    return new SHA384Managed();
                default:
                    return new SHA512Managed();
            }
        }
    }
}
